package org.rowan.adi;

/**
 * Represents a 3D vector in Cartesian values. All created Vectors remain 
 * immutable. 2D vectors can be represented by creating a Vector with a z component
 * of 0.
 * 
 * @author Spence DiNicolantonio
 * @version 1.0
 */
public class Vector {
	
	/*********************************************
	 * MARK: Constants
	 *********************************************/	
	
	/* A zero vector */
	public static final Vector ZERO_VECTOR = new Vector(0, 0, 0);
	
	/* The cartesian origin */
	public static final Vector ORIGIN = ZERO_VECTOR;
	
	/* A normalized vector representing the cartesian x axis */
	public static final Vector X_AXIS = new Vector(1, 0, 0);
	
	/* A normalized vector representing the cartesian y axis */
	public static final Vector Y_AXIS = new Vector(0, 1, 0);
	
	/* A normalized vector representing the cartesian z axis */
	public static final Vector Z_AXIS = new Vector(0, 0, 1);
	
	
	/*********************************************
	 * MARK: Fields
	 *********************************************/	
	
	/* The x component of this vector. */
	private double x;
	
	/* The y component of this vector. */
	private double y;
	
	/* The z component of this vector. */
	private double z;
	
	
	/*********************************************
	 * MARK: Constructors
	 *********************************************/	
	
	/**
	 * Designated constructor.
	 * <p>
	 * Creates a Vector with given x, y, and z components values.
	 * @param x	The x component to be set in the created Vector.
	 * @param y The y component to be set in the created Vector.
	 * @param z The z component to be set in the created Vector.
	 */
	public Vector(double x, double y, double z) {
		// set component values
		this.x = x;
		this.y = y;
		this.z = z;
		
		// normalize zero value components
		normalizeZeros();
	}
	
	/**
	 * Creates a Vector with given magnitude and direction.
	 * <p>
	 * If 0 or a zero vector are given for the magnitude or direction respectively,
	 * a directionless zero vector will be created.
	 * @param magnitude	The magnitude of the created Vector.
	 * @param direction	A Vector describing the direction of the created Vector.
	 */
	public Vector(double magnitude, Vector direction) {
		// copy given direction vector
		this(direction.x, direction.y, direction.z);
		
		// set magnitude to given value
		setMagnitude(magnitude);
	}
	
	/**
	 * Creates a Vector with given x and y component values. This constructor 
	 * can be used when working with only 2 dimensions. The z component of the
	 * created Vector will be set to 0.
	 * @param x	The x component to be set in the created Vector.
	 * @param y The y component to be set in the created Vector.
	 */
	public Vector(double x, double y) {
		// call designated constructor
		this(x, y, 0);
	}
	
	
	/*********************************************
	 * MARK: Accessors
	 *********************************************/	
	
	/**
	 * Returns the x component of this Vector.
	 * @return	The x component of this Vector.
	 */
	public double getX() {
		return this.x;
	}
	
	/**
	 * Returns the y component of this Vector.
	 * @return	The y component of this Vector.
	 */
	public double getY() {
		return this.y;
	}
	
	/**
	 * Returns the z component of this Vector.
	 * @return	The z component of this Vector.
	 */
	public double getZ() {
		return this.z;
	}
	
	
	/*********************************************
	 * MARK: Queries
	 *********************************************/	
	
	/**
	 * Calculates the magnitude of this Vector.
	 * @return The magnitude of this Vector.
	 */
	public double magnitude() {
		// calculate magnitude using the Pythagorian theorem
		return Math.sqrt(x*x + y*y + z*z);
	}
	
	/**
	 * Determines whether this Vector is a zero vector. A zero vector is a 
	 * directionless vector with 0 magnitude (all component values equal 0).
	 * @return	<code>true</code> if this Vector is a zero vector; <code>false</code>
	 *			otherwise.
	 */
	public boolean isZeroVector() {
		return (magnitude() == 0);
	}
	
	/**
	 * Determines whether this Vector is a unit vector. A unit vector is a vector
	 * with magnitude of 1.0.
	 * @return	<code>true</code> if this Vector is a unit vector; <code>false</code>
	 *			otherwise.
	 */
	public boolean isUnitVector() {
		return (magnitude() == 1);
	}
	
	
	/*********************************************
	 * MARK: Arithmetic
	 *********************************************/	
	
	/**
	 * Creates a unit vector with the same direction as this Vector. If this Vector
	 * is a zero vector, a zero vector is returned.
	 * @return	A unit vector with the same direction as this Vector. A zero vector
	 *			if this Vector is a zero vector.
	 */
	public Vector unitVector() {
		// copy vector
		Vector unitVector = new Vector(this.x, this.y, this.z);
		
		// normalize and return copy
		unitVector.normalize();
		return unitVector;
	}
	
	/**
	 * Calculates the vector inverse of this Vector.
	 * @return	A Vector with the same magnitude as this Vector, but opposite 
	 *			direction.
	 */
	public Vector inverse() {
		return new Vector(-x, -y, -z);
	}
	
	/**
	 * Adds a given Vector to this Vector. Note that adding a Vector's inverse
	 * to itself will yield a directionless zero vector. 
	 * @param vector	A Vector to be added to this vector.
	 * @return			The vector sum of a given vector and this Vector.
	 */
	public Vector add(Vector vector) {
		// add components
		double x = this.x + vector.x;
		double y = this.y + vector.y;
		double z = this.z + vector.z;
		
		// create and return new Vector
		return new Vector(x, y, z);
	}
	
	/**
	 * Subtracts a given Vector from this Vector. Note that subtracting a Vector
	 * by itself or another equivalent vector will yield a directionless zero vector.
	 * @param vector	A Vector to be subtracted from this vector.
	 * @return			The vector difference between this Vector and a given Vector.
	 */
	public Vector subtract(Vector vector) {
		// subtract components
		double x = this.x - vector.x;
		double y = this.y - vector.y;
		double z = this.z - vector.z;
		
		// create and return new Vector
		return new Vector(x, y, z);
	}
	
	/**
	 * Multiplies this Vector by a given scalar value. Note that multiplying a
	 * Vector by 0.0 will yield a directionless zero vector.
	 * @param value	A scalar value to be multiplied by this Vector.
	 * @return		The product of this Vector and a given scalar value.
	 */
	public Vector multiply(double value) {
		// copy vector
		Vector product = new Vector(this.x, this.y, this.z);
		
		// multiply copied components 
		product.x *= value;
		product.y *= value;
		product.z *= value;
		
		// return product
		return product;
	}
	
	/**
	 * Calculates the scalar dot product of this Vector and a given Vector.
	 * @param vector	A Vector to be "dotted" with this Vector.
	 * @return			The scalar dot product of this Vector and the given Vector.
	 */
	public double dot(Vector vector) {
		return (this.x * vector.x) + (this.y * vector.y) + (this.z * vector.z);
	}
	
	/**
	 * Calculates the vector cross product of this Vector and a given Vector.
	 * @param vector	A Vector to be "crossed" with this Vector.
	 * @return			The vector cross product of this Vector and the given vector.
	 */
	public Vector cross(Vector vector) {
		// calculate component values
		double x = (this.y * vector.z) - (vector.y * this.z);
		double y = (this.z * vector.x) - (vector.z * this.x);
		double z = (this.x * vector.y) - (vector.x * this.y);
		
		// create and return cross product
		return new Vector(x, y, z);
	}
	
	
	/*********************************************
	 * MARK: toString
	 *********************************************/	
	
	/**
	 * Creates a string to describe this Vector.
	 * @return	A string that describes this Vector.
	 */
	public String toString() {
		return ("("+this.x+", "+this.y+", "+this.z+")");
	}
	
	
	/*********************************************
	 * MARK: Private
	 *********************************************/
	
	/**
	 * Normalizes this Vector to a unit vector with the same direction. If this 
	 * Vector is a zero vector, this method has no effect.
	 */
	private void normalize() {
		// zero vector?
		if (isZeroVector())
			return;
		
		// normalize to magnitude of 1.0
		double magnitude = magnitude();
		this.x /= magnitude;
		this.y /= magnitude;
		this.z /= magnitude;
		normalizeZeros();
	}
	
	
	
	/**
	 * Normalizes -0.0 value components in this Vector to 0.0. This method is used
	 * by the Vector constructor to maintain aesthetics.
	 */
	private void normalizeZeros() {
		// if value is -0.0 set it to 0.0
		this.x = (this.x == 0.0) ? 0.0 : this.x;
		this.y = (this.y == 0.0) ? 0.0 : this.y;
		this.z = (this.z == 0.0) ? 0.0 : this.z;
	}
	
	/**
	 * Sets this Vector's magnitude to a given value. Note that setting the magnitude
	 * to 0.0 will result in a directionless zero vector. Assuming the given 
	 * magnitude is not 0.0, this Vector's direction will be maintained.
	 * @param magnitude	The desired magnitude for this Vector.
	 */
	private void setMagnitude(double magnitude) {
		// zero magnitude?
		if (magnitude == 0.0) {
			this.x = 0.0;
			this.y = 0.0;
			this.z = 0.0;
		}
		
		// calculate component values
		double currentMagnitude = magnitude();
		this.x *= (magnitude / currentMagnitude);
		this.y *= (magnitude / currentMagnitude);
		this.z *= (magnitude / currentMagnitude);
	}
	
	/**
	 * Sets this Vector's direction to that of a given Vector. The magnitude of
	 * this Vector will remain unchanged.
	 * @param direction	A Vector defining the desired direction for this Vector.
	 */
	private void setDirection(Vector direction) {
		// store current magnitude
		double mag = magnitude();
		
		// copy component values from the given Vector
		this.x = direction.x;
		this.y = direction.y;
		this.z = direction.z;
		
		// set magnitude to previous
		setMagnitude(mag);
	}
}

